package com.sds.prolog.interpreter;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.sds.prolog.model.Answer;
import com.sds.system.models.IdentifiedObject;
import com.sds.prolog.model.Laptop;
import com.sds.prolog.model.Question;
import com.sds.prolog.model.Rule;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

public class InterpreterUtils {

    private static Logger LOGGER = Logger.getLogger(InterpreterUtils.class.getName());

    private static final String TOKEN_KEY_PREFIX = "_TK";
    private static long TOKEN_KEY_IDENTITY = 0;

    private static final String SET_KEY_PREFIX = "_SK";
    private static long SET_KEY_IDENTITY = 0;

    private static final String RULE_KEY_PREFIX = "Rule_";


    private static final String QUESTION = "cau_hoi";
    private static final String RULE = "luat";
    private static final String LAPTOP = "laptop";
    private static final String SET_SYMBOL = ":-";
    private static final char QUOTE = '"';
    private static final String COMMA = ",";

    public static IdentifiedObject interpret(String line) {
        try {
            Map<String, String> tokens = Maps.newHashMap();
            String refinedLine = tokenizeString(line.trim(), tokens);
            refinedLine = buildSet(refinedLine, tokens);
            if (refinedLine.startsWith(QUESTION)) {
                return interpretQuestion(refinedLine.substring(QUESTION.length()), tokens);
            } else if (refinedLine.startsWith(RULE)) {
                return interpretRule(refinedLine.substring(RULE.length()), tokens);
            } else if (refinedLine.startsWith(LAPTOP)) {
                return interpretLaptop(refinedLine.substring(LAPTOP.length()), tokens);
            } else {
                throw new IllegalArgumentException("Unknown content type.");
            }
        } catch (Exception ex) {
            LOGGER.severe("ERROR in line:" + line);
            throw new RuntimeException(ex);
        }

    }

    protected static Question interpretQuestion(String line, Map<String, String> tokens) {
        Question result = new Question();
        String[] tempArray = line.split(SET_SYMBOL);
        result.setId(tempArray[1]);
        line = tokens.get(tempArray[0]);
        tempArray = line.split(COMMA);
        result.setQuestion(tokens.get(tempArray[0]));
        for (int i = 1; i < tempArray.length; i++) {
            String[] tempArray2 = tempArray[i].split(SET_SYMBOL);
            Answer answer = new Answer();
            answer.setText(tokens.get(tempArray2[0]));
            answer.setTargets(parseObject(tempArray2[1], tokens));
            result.getAnswers().add(answer);
        }
        return result;
    }

    protected static Rule interpretRule(String line, Map<String, String> tokens) {
        Rule rule = new Rule();
        String[] tempArray = line.split(SET_SYMBOL);
        rule.setMaterial(parseObject(tempArray[0], tokens));
        rule.setProduces(parseObject(tempArray[1], tokens));
        rule.setId(RULE + rule.getMaterial().hashCode() + "_" + rule.getProduces().hashCode());
        return rule;
    }

    protected static Laptop interpretLaptop(String line, Map<String, String> tokens) {
        Laptop laptop = new Laptop();
        String[] tempArray = line.split(SET_SYMBOL);
        laptop.setId(tempArray[1]);
        tempArray = tokens.get(tempArray[0]).split(",");
        laptop.setName(tokens.get(tempArray[0]));
        laptop.setUrl(tokens.get(tempArray[1]));
        laptop.setRefinedAttribute(parseObject(tempArray[2], tokens));
        return laptop;
    }


    private static HashSet<String> parseObject(String content, Map<String, String> tokens) {
        return parseObject(tokens.containsKey(content) ? tokens.get(content) : content);
    }

    private static HashSet<String> parseObject(String content) {
        HashSet<String> result = Sets.newHashSet();
        for (String string : content.split(COMMA)) {
            result.add(string);
        }
        return result;
    }

    protected static String buildSet(String source, Map<String, String> setMap) {
        String result = source;
        List<Integer> stack = Lists.newArrayList();
        Set<String> keys = Sets.newHashSet();
        for (int i = 0; i < source.length(); i++) {
            char ch = source.charAt(i);
            if (ch == '(') {
                stack.add(0, i);
            } else if (ch == ')') {
                if (stack.size() > 0) {
                    String key = getNextSetKey();
                    String value = source.substring(stack.get(0) + 1, i);
                    for (String iKey : keys) {
                        value = value.replace("(" + setMap.get(iKey) + ")", iKey);
                    }
                    setMap.put(key, value);
                    keys.add(key);
                    result = result.replace("(" + setMap.get(key) + ")", key);
                    stack.remove(0);
                }
            }
        }
        if (stack.size() > 0) {
            throw new RuntimeException(new IllegalArgumentException("Unclosed string literal:" + source));
        }
        return result;
    }

    protected static String tokenizeString(String source, Map<String, String> tokenMap) {
        StringBuilder result = new StringBuilder();
        int current = 0;
        int start = source.indexOf(QUOTE);
        int end = source.indexOf(QUOTE, start + 1);
        while (start >= 0) {
            if (start >= 0 && end >= 0) {
                result.append(source.substring(current, start));
                String key = getNextTokenKey();
                result.append(key);
                tokenMap.put(key, source.substring(start + 1, end));
            }
            if (end == -1) {
                throw new RuntimeException(new IllegalArgumentException("Unclosed string literal:" + source));
            }
            current = end + 1;
            start = source.indexOf(QUOTE, end + 1);
            end = source.indexOf(QUOTE, start + 1);
        }
        result.append(source.substring(current));
        return result.toString();
    }

    private static String getNextTokenKey() {
        return TOKEN_KEY_PREFIX + TOKEN_KEY_IDENTITY++;
    }

    private static String getNextSetKey() {
        return SET_KEY_PREFIX + SET_KEY_IDENTITY++;
    }

}
